package client

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"longmen/server/config/global"
	"longmen/server/pkg/common/payment"
	"longmen/server/pkg/common/helper/encrypt"
	"longmen/server/pkg/common/helper/util"
	"net/http"
	"net/url"
	"regexp"
	"strings"
)

const (
	// 支付名稱
	gfpPayName = "GFPPAY"
)

func init() {
	err := payment.RegisterPay(gfpPayName, initGfpPayClient)
	if err != nil {
		global.Log.Error("msg", "register gfppay error", "err", err)
		return
	}
}

func initGfpPayClient(api payment.PayApi) (payment.PayClient, error) {
	var client *GfpPayClient
	if err := json.Unmarshal([]byte(api.Param), &client); err != nil {
		return nil, err
	}
	client.Host = api.ReqUri
	client.MID = api.Mid
	client.Key = api.Secret

	return client, nil
}

// GfpPayClient astropay client
//
// api 文件
//  - https://developers-wallet.astropay.com/
type GfpPayClient struct {
	Host string `json:"host"`
	MID  string `json:"merchantAPIKey"`
	Key  string `json:"merchantSecretKey"`
	//custom params
	Version string `json:"version"`
	Gateway string `json:"gateway"`
}

// Sn 產生訂單編號
func (c *GfpPayClient) Sn() string {
	return ""
}

// Pay 支付流程
func (c *GfpPayClient) Pay(charge *payment.Charge) (*payment.PayInfo, error) {
	fName := "Pay"
	m1 := regexp.MustCompile(`#.*`)
	FirstName := charge.Username
	if FirstName == "" {
		FirstName = charge.FirstName
	}
	LastName := charge.Username
	if LastName == "" {
		LastName = charge.FirstName
	}
	req := &GfpPayDepositRequest{
		Version:     c.Version,
		Scode:       c.MID,
		OrderID:     charge.OrderSn,
		Amount:      fmt.Sprintf("%0.2f", charge.Money), // 小數點後兩位 00.00
		Gateway:     charge.Type,
		NoticeURL:   charge.NotifyURL,
		RedirectURL: m1.ReplaceAllString(charge.BackURL, "#/userCentre/depositRecord"),
		ProductName: charge.OrderSn,
		Memo:        charge.OrderSn,
		FirstName:   FirstName,
		LastName:    LastName,
		UserId:      util.I2S(charge.UserId),
	}
	fmt.Printf("===========支付pay (inside)==========\n")
	fmt.Printf("req: %v\n", req)

	redirectHTML, err := c.GfpPayDepositNew(req, charge.Mobile)
	if err != nil {
		return nil, fmt.Errorf("%s%s", fName, err.Error())
	}
	info := &payment.PayInfo{
		Type:    "FORM",
		Content: redirectHTML,
		ThirdSn: "",
	}
	return info, nil
}

// [" 入金 callback 簽名驗證
func (c *GfpPayClient) VerifySign(r *http.Request) (*payment.BaseResult, error) {
	fmt.Printf("===========支付回调 (inside)==========\n")
	//req := new(GfpPayDepositCallbackRequest)
	var buf string

	req := r.Form

	body, _ := ioutil.ReadAll(r.Body)
	fmt.Printf("Form: %v\n", r.Form)
	fmt.Printf("body: %v\n", body)

	buf = buf + "status=" + req.Get("status") + "&"
	buf = buf + "shid=" + req.Get("shid") + "&"
	buf = buf + "bb=" + c.Version + "&"
	buf = buf + "zftd=" + req.Get("zftd") + "&"
	buf = buf + "ddh=" + req.Get("ddh") + "&"
	buf = buf + "je=" + req.Get("je") + "&"
	buf = buf + "ddmc=" + req.Get("ddmc") + "&"
	buf = buf + "ddbz=" + req.Get("ddbz") + "&"
	buf = buf + "ybtz=" + req.Get("ybtz") + "&"
	buf = buf + "tbtz=" + req.Get("tbtz") + "&"
	buf = buf + c.Key
	signatureNew := encrypt.Md5(buf)

	if req.Get("sign") != signatureNew {
		fmt.Printf("%v callback invalid signature. request: %v. request signature: %v. valid signature:%v\n", gfpPayName, buf, req.Get("sign"), signatureNew)
		return nil, fmt.Errorf("%v callback invalid signature. request: %v. request signature: %v. valid signature:%v", gfpPayName, buf, req.Get("sign"), signatureNew)
	}
	result := &payment.BaseResult{
		OrderSn:   req.Get("ddh"),
		ThirdSn:   req.Get("ddh"),
		IsSucceed: true,
		Params:    buf,
		IsManual:  util.S2I(req.Get("isManual")),
	}
	switch (GfpPayDepositStatus)(req.Get("status")) {
	case GfpPayDepositStatusProcess:
		result.Status = 0
	case GfpPayDepositStatusSuccess:
		result.Status = 1
	case GfpPayDepositStatusFailure:
		result.Status = 2
	}
	fmt.Printf("result: %v\n", result)
	return result, nil
}

// Notify callback 返回三方格式
func (c *GfpPayClient) Notify(br *payment.BaseResult) *payment.BaseNotify {
	fName := "gfpPay.Notify"

	notify := &payment.BaseNotify{
		Type: payment.STRING,
		Ret:  "success",
	}
	fmt.Printf("%s 返回三方：%v\n", fName, notify)
	return notify
}

// Withdrawal 出金流程
func (c *GfpPayClient) Withdrawal(charge *payment.Charge, withdrawal *payment.Withdrawal) (*payment.BaseResult, error) {
	req := GfpPayWithdrawalRequest{
		Scode:       c.MID,
		OrderID:     charge.OrderSn,
		Money:       fmt.Sprintf("%0.2f", charge.Money),
		BankName:    withdrawal.BankName,
		AccountNo:   withdrawal.Card,
		AccountName: withdrawal.Name,
		BankNo:      withdrawal.BankCode,
		NotifyURL:   charge.NotifyURL,
	}
	_, err := c.GfpPayWithdrawal(&req)
	if err != nil {
		return nil, err
	}
	return &payment.BaseResult{
		IsSucceed: true,
		OrderSn:   charge.OrderSn,
		ThirdSn:   charge.OrderSn,
		Money:     charge.Money,
	}, nil
}

// WithdrawalSign 出金 callback 簽名驗證
func (c *GfpPayClient) WithdrawalSign(r *http.Request) (*payment.BaseResult, error) {
	req := new(GfpPayWithdrawalCallbackRequest)
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, fmt.Errorf("%v callback error. request: %v. error: %v", gfpPayName, string(body), err)
	}
	if err := json.Unmarshal(body, req); err != nil {
		return nil, fmt.Errorf("%v callback error. request: %v. error: %v", gfpPayName, string(body), err)
	}
	signature, err := c.generateSignature(req)
	if err != nil {
		return nil, err
	}
	if req.Sign != signature {
		return nil, fmt.Errorf("%v callback invalid signature. request: %v. request signature: %v. valid signature:%v", gfpPayName, string(body), r.Header.Get("Signature"), signature)
	}
	result := &payment.BaseResult{
		OrderSn:   req.OrderID,
		ThirdSn:   req.OrderID,
		IsSucceed: true,
	}
	switch req.Status {
	case GfpPayWithdrawalStatusProcess:
		result.Status = 0
	case GfpPayWithdrawalStatusSuccess:
		result.Status = 1
	case GfpPayWithdrawalStatusFailure:
		result.Status = 2
	}
	return result, nil
}

// GfpPayDeposit is ofapay deposit api
func (c *GfpPayClient) GfpPayDepositNew(req *GfpPayDepositRequest, mobile bool) (redirectHTML string, err error) {

	var buf string
	buf = buf + "shid=" + req.Scode + "&"
	buf = buf + "bb=" + c.Version + "&"
	//buf = buf + "zftd=" + req.Gateway + "&"
	buf = buf + "ddh=" + req.OrderID + "&"
	buf = buf + "je=" + req.Amount + "&"
	buf = buf + "ddmc=" + req.ProductName + "&"
	buf = buf + "ddbz=" + req.Memo + "&"
	buf = buf + "ybtz=" + req.NoticeURL + "&"
	buf = buf + "tbtz=" + req.RedirectURL + "&"
	buf = buf + "xing=" + req.LastName + "&"
	buf = buf + "ming=" + req.FirstName + "&"
	buf = buf + c.Key
	signatureNew := encrypt.Md5(buf)

	req.Sign = signatureNew

	//urlPath := "/pay/api.php"
	values := url.Values{}

	values.Add("bb", c.Version)
	values.Add("shid", req.Scode)
	values.Add("ddh", req.OrderID)
	values.Add("je", req.Amount)
	//values.Add("zftd", req.Gateway)
	values.Add("ybtz", req.NoticeURL)
	values.Add("tbtz", req.RedirectURL)
	values.Add("ddmc", req.ProductName)
	values.Add("ddbz", req.Memo)
	values.Add("sign", req.Sign)
	values.Add("xing", req.LastName)
	values.Add("ming", req.FirstName)
	values.Add("userid", req.UserId)

	resp := GfpPayDepositResponse{
		Status: "200",
		Method: "POST",
		Url:    c.Host,
		Data:   values,
	}
	return c.ToHTML(resp, mobile)
}

// GfpPayDeposit is ofapay deposit api
func (c *GfpPayClient) GfpPayDeposit(req *GfpPayDepositRequest, mobile bool) (redirectHTML string, err error) {

	var buf string
	buf = buf + "shid=" + req.Scode + "&"
	buf = buf + "bb=" + c.Version + "&"
	//buf = buf + "zftd=" + req.Gateway + "&"
	buf = buf + "ddh=" + req.OrderID + "&"
	buf = buf + "je=" + req.Amount + "&"
	buf = buf + "ddmc=" + req.ProductName + "&"
	buf = buf + "ddbz=" + req.Memo + "&"
	buf = buf + "ybtz=" + req.NoticeURL + "&"
	buf = buf + "tbtz=" + req.RedirectURL + "&"
	buf = buf + c.Key
	sign := encrypt.Md5(buf)

	req.Sign = sign

	//urlPath := "/pay/api.php"
	urlPath := ""
	values := url.Values{}

	values.Add("bb", c.Version)
	values.Add("shid", req.Scode)
	values.Add("ddh", req.OrderID)
	values.Add("je", req.Amount)
	//values.Add("zftd", req.Gateway)
	values.Add("ybtz", req.NoticeURL)
	values.Add("tbtz", req.RedirectURL)
	values.Add("ddmc", req.ProductName)
	values.Add("ddbz", req.Memo)
	values.Add("sign", req.Sign)

	response, err := http.Post(c.Host+urlPath, "application/x-www-form-urlencoded", strings.NewReader(values.Encode()))
	if err != nil {
		return "", err
	}
	defer response.Body.Close()

	var resp GfpPayDepositResponse
	byteResp, err := ioutil.ReadAll(response.Body)
	//return (string)(byteResp), err
	if err != nil {
		return "", err
	}

	if err := json.Unmarshal(byteResp, &resp); err != nil || resp.Status != "200" {
		return (string)(byteResp), errors.New((string)(byteResp))
	}

	if resp.Status != "200" {
		return "", errors.New(resp.Msg)
	}

	return c.ToHTML(resp, mobile)
}

// ToHTML 转换form表单
func (c *GfpPayClient) ToHTML(resp GfpPayDepositResponse, mobile bool) (string, error) {

	inputs := ""
	targetBlank := ""
	if mobile {
		targetBlank = `target="_blank"`
		inputs += `<button type='submit' class='v-btn v-btn--block v-btn--large v-btn--depressed v-btn--round theme--dark primary'>确认</button>`
	}
	_url := resp.Url + "?"
	data := resp.Data.(url.Values)
	for key := range data {
		inputs += fmt.Sprintf(`<input type="hidden" name="%s" value="%s" />`, key, data.Get(key))
		_url += fmt.Sprintf("%s=%s&", key, data.Get(key))
	}
	method := resp.Method

	if method == "" {
		method = "POST"
	}

	form := fmt.Sprintf(`<form name="pay" method="%s" action="%s" %s>%s</form>`,
		method, _url, targetBlank, inputs)

	return form, nil
}

// GfpPayDepositEnquiry is ofapay deposit enquiry api
func (c *GfpPayClient) GfpPayDepositEnquiry(req *GfpPayDepositEnquiryRequest) (*GfpPayDepositEnquiryResponse, error) {
	sign, err := c.generateSignature(&req)
	if err != nil {
		return nil, err
	}
	req.Sign = sign

	urlPath := "/pay/query.aspx"
	resp := &GfpPayDepositEnquiryResponse{}
	err = c.do(urlPath, "application/json", req, resp)
	if err != nil {
		return nil, err
	}
	// 00 Transaction success
	// 01 No such transaction
	// 10 Uncompleted deposit transaction
	if !(resp.RespCode == "00" || resp.RespCode == "01" || resp.RespCode == "10") {
		return nil, fmt.Errorf("ofa pay deposit error: %v", resp)
	}
	return resp, nil
}

// GfpPayWithdrawal is ofapay deposit api
func (c *GfpPayClient) GfpPayWithdrawal(req *GfpPayWithdrawalRequest) (*GfpPayWithdrawalResponse, error) {
	sign, err := c.generateSignature(&req)
	if err != nil {
		return nil, err
	}
	req.Sign = sign

	urlPath := "/betdf/df.aspx"
	resp := &GfpPayWithdrawalResponse{}
	err = c.do(urlPath, "application/json", req, resp)
	if err != nil {
		return nil, err
	}
	if resp.Prc != "1" {
		return nil, fmt.Errorf("ofa pay withdrawal error: %v", resp)
	}
	return resp, nil
}

// GfpPayWithdrawalEnquiry is ofapay deposit api
func (c *GfpPayClient) GfpPayWithdrawalEnquiry(req *GfpPayWithdrawalEnquiryRequest) (*GfpPayWithdrawalEnquiryResponse, error) {
	sign, err := c.generateSignature(&req)
	if err != nil {
		return nil, err
	}
	req.Sign = sign

	urlPath := "/betdf/querydf.aspx"
	resp := &GfpPayWithdrawalEnquiryResponse{}
	err = c.do(urlPath, "application/json", req, resp)
	if err != nil {
		return nil, err
	}
	if resp.Prc != "1" {
		return nil, fmt.Errorf("ofa pay withdrawal enquiry error: %v", resp)
	}
	return resp, nil
}

// generateSignature denerate ofapay api signature
func (c *GfpPayClient) generateSignature(req interface{}) (string, error) {
	var buf strings.Builder
	reqMap := map[string]interface{}{}
	reqBody, err := json.Marshal(req)
	if err != nil {
		return "", err
	}
	err = json.Unmarshal(reqBody, &reqMap)
	if err != nil {
		return "", err
	}
	keys := make([]string, 0, len(reqMap))

	keys = append(keys, "bb")
	keys = append(keys, "shid")
	keys = append(keys, "ddh")
	keys = append(keys, "je")
	//keys = append(keys, "zftd")
	keys = append(keys, "ybtz")
	keys = append(keys, "tbdz")
	keys = append(keys, "ddmc")
	keys = append(keys, "ddbz")

	for _, k := range keys {
		v := reqMap[k]
		if buf.Len() > 0 {
			buf.WriteByte('&')
		}
		buf.WriteString(k)
		buf.WriteByte('=')
		buf.WriteString(fmt.Sprint(v))
	}
	//return buf.String() + "&" + c.Key, nil
	return encrypt.Md5(buf.String() + "&" + c.Key), nil
}

// do is execute ofa pay api
func (c *GfpPayClient) do(urlPath string, contentType string, req interface{}, refResp interface{}) error {
	var dataByte []byte
	var reqBody io.Reader
	dataByte, err := json.Marshal(req)
	if err != nil {
		return err
	}
	reqBody = bytes.NewReader(dataByte)
	response, err := http.Post(c.Host+urlPath, contentType, reqBody)
	if err != nil {
		return err
	}
	defer response.Body.Close()
	respBody, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(respBody, refResp)
	if err != nil {
		return err
	}
	return nil
}

// GfpPayDepositRequest is ofapay deposit api request
type GfpPayDepositRequest struct {
	Scode       string `json:"shid"`
	Version     string `json:"bb"`
	OrderID     string `json:"ddh"`
	Amount      string `json:"je"`
	ProductName string `json:"ddmc"`
	Memo        string `json:"ddbz"`
	NoticeURL   string `json:"ybtz"`
	RedirectURL string `json:"tbdz"`
	Gateway     string `json:"zftd"`
	FirstName   string `json:"ming"`
	LastName    string `json:"xing"`
	Sign        string `json:"sign"`
	UserId      string `json:"userid"`
}

// GfpPayDepositResponse is ofapay deposit api Response
type GfpPayDepositResponse struct {
	Status string      `json:"status"`
	Url    string      `json:"url"`
	Msg    string      `json:"msg"`
	Method string      `json:"method"`
	Data   interface{} `json:"data"`
}

// GfpPayDepositErrorResponse ofa pay deposit error response
type GfpPayDepositErrorResponse struct {
	Status   string `json:"status"`
	Respcode string `json:"respcode"`
	Respmsg  string `json:"respmsg"`
}

// GfpPayDepositEnquiryRequest is ofapay deposit enquiry api request
type GfpPayDepositEnquiryRequest struct {
	Scode   string `json:"scode"`
	OrderID string `json:"orderid"`
	Sign    string `json:"sign"`
}

// GfpPayDepositEnquiryResponse is ofapay deposit enquiry api response
type GfpPayDepositEnquiryResponse struct {
	Scode       string              `json:"scode"`
	OrderID     string              `json:"orderid"`
	OrderNo     string              `json:"orderno"`
	Paytype     string              `json:"paytype"`
	Amount      string              `json:"amount"`
	ProductName string              `json:"productname"`
	Currency    string              `json:"currency"`
	Memo        string              `json:"memo"`
	Resptime    string              `json:"resptime"`
	Status      GfpPayDepositStatus `json:"status"`
	RespCode    string              `json:"respcode"`
	Sign        string              `json:"sign"`
}

// GfpPayDepositCallbackRequest is ofapay deposit callback api response
type GfpPayDepositCallbackRequest struct {
	Status      GfpPayDepositStatus `json:"status"`
	Scode       string              `json:"shid"`
	OrderID     string              `json:"ddh"`
	Amount      string              `json:"je"`
	ProductName string              `json:"ddmc"`
	Memo        string              `json:"ddbz"`
	NoticeURL   string              `json:"ybtz"`
	RedirectURL string              `json:"tbdz"`
	Gateway     string              `json:"zftd"`
	Sign        string              `json:"sign"`
}

// GfpPayWithdrawalRequest is ofapay deposit withdrawal api request
type GfpPayWithdrawalRequest struct {
	Scode       string `json:"scode"`
	OrderID     string `json:"orderid"`
	Money       string `json:"money"`
	BankName    string `json:"bankname"`
	AccountNo   string `json:"accountno"`
	AccountName string `json:"accountname"`
	BankNo      string `json:"bankno"`
	Province    string `json:"province"`
	City        string `json:"city"`
	NotifyURL   string `json:"notifyurl"`
	Sign        string `json:"sign"`
}

// GfpPayWithdrawalCallbackRequest is ofapay withdrawal callback api response
type GfpPayWithdrawalCallbackRequest struct {
	Scode    string                 `json:"scode"`
	OrderID  string                 `json:"orderid"`
	OrderNo  string                 `json:"orderno"`
	Money    string                 `json:"money"`
	Status   GfpPayWithdrawalStatus `json:"status"`
	RespCode string                 `json:"respcode"`
	RespTime string                 `json:"resptime"`
	Sign     string                 `json:"sign"`
}

// GfpPayWithdrawalResponse is ofapay deposit withdrawal api response
type GfpPayWithdrawalResponse struct {
	Prc     string `json:"prc"` // 1: Request Success, -1: Request Failure
	Errcode string `json:"errcode"`
	Msg     string `json:"msg"`
	OrderNo string `json:"orderno"`
}

// GfpPayWithdrawalEnquiryRequest is ofapay withdrawal enquiry api request
type GfpPayWithdrawalEnquiryRequest struct {
	Scode   string `json:"scode"`
	OrderID string `json:"orderid"`
	Sign    string `json:"sign"`
}

// GfpPayWithdrawalEnquiryResponse is ofapay withdrawal enquiry api response
type GfpPayWithdrawalEnquiryResponse struct {
	Prc      string                 `json:"prc"`
	Errcode  string                 `json:"errcode"`
	Scode    string                 `json:"scode"`
	OrderID  string                 `json:"orderid"`
	OrderNo  string                 `json:"orderno"`
	Money    string                 `json:"money"`
	Status   GfpPayWithdrawalStatus `json:"status"`
	Msg      string                 `json:"msg"`
	Resptime string                 `json:"resptime"`
	Sign     string                 `json:"sign"`
}

// GfpPayDepositStatus deposit 結果
type GfpPayDepositStatus string

const (
	// GfpPayDepositStatusSuccess 成功
	GfpPayDepositStatusSuccess GfpPayDepositStatus = "success"
	// GfpPayDepositStatusProcess 處理中
	GfpPayDepositStatusProcess GfpPayDepositStatus = "-"
	// GfpPayDepositStatusFailure 失敗
	GfpPayDepositStatusFailure GfpPayDepositStatus = "fail"
)

// GfpPayWithdrawalStatus 出金結果
type GfpPayWithdrawalStatus string

const (
	// GfpPayWithdrawalStatusProcess 處理中
	GfpPayWithdrawalStatusProcess GfpPayWithdrawalStatus = "I"
	// GfpPayWithdrawalStatusSuccess 成功
	GfpPayWithdrawalStatusSuccess GfpPayWithdrawalStatus = "S"
	// GfpPayWithdrawalStatusFailure 失敗
	GfpPayWithdrawalStatusFailure GfpPayWithdrawalStatus = "F"
)
